package org.dreamwork.drawing.framework;

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.util.Vector;
import java.util.ArrayList;

/**
 * Created by IntelliJ IDEA.
 * User: seth
 * Date: 2009-9-29
 * Time: 17:22:58
 */
public class DrawingCanvas extends JComponent {
    public static final int MODE_NORMAL = 0;
    public static final int MODE_DRAWING_FIGURE = 1;
    public static final int MODE_DRAG_FIGURE = 2;
    public static final int MODE_RESIZE_FIGURE = 3;
    public static final int MODE_DRAWING_LINE = 4;
    public static final int MODE_DRAG_LINE = 5;

    protected static final Cursor CURSOR_NW_SE = Cursor.getPredefinedCursor (Cursor.NW_RESIZE_CURSOR);
    protected static final Cursor CURSOR_NE_SW = Cursor.getPredefinedCursor (Cursor.NE_RESIZE_CURSOR);
    protected static final Cursor CURSOR_SN = Cursor.getPredefinedCursor (Cursor.N_RESIZE_CURSOR);
    protected static final Cursor CURSOR_WE = Cursor.getPredefinedCursor (Cursor.E_RESIZE_CURSOR);
    protected static final Cursor CURSOR_DEFAULT = Cursor.getDefaultCursor ();

    private static final int filler = 25;

    protected Vector<Figure> figures = new Vector<Figure> ();
    protected Vector<Line> lines = new Vector<Line> ();

    protected transient int x, y;
    protected transient int prevx = -1, prevy = -1;
    protected transient Figure selectedFigure, draggingFigure;
    protected transient Line selectedLine;
    protected transient Point current;
    protected transient boolean dragging, resizing, selecting;
    protected transient boolean changed = false;

    private transient Dimension dim;
    private transient int drawing_mode = MODE_NORMAL;
    private transient int handler;
    private transient Point start = new Point ();
    private transient Dimension d = new Dimension ();

    protected MouseListener ml = new MouseAdapter() {
        public void mouseClicked (MouseEvent e) {
            Figure f = selectFigure (e.getPoint ());
            if (e.getButton () == 1) unselectAll ();
            if (f != null) {
                if (e.getButton () == 1) {
                    f.select ();
                    selectedFigure = f;
                    figures.remove (f);
                    figures.add (f);
                }
                f.fireMouseClicked (e);
            } else {
                for (int i = 0; i < lines.size (); i++) {
                    Line line = lines.elementAt (i);
                    if (line.contains (e.getPoint ())) {
                        line.selecte ();
                        selectedLine = line;
                        selectedLine.fireMouseClick (e);
                        break;
                    }
                }

                if (selectedLine == null) unselectAll ();
            }
            repaint ();
        }

        public void mousePressed (MouseEvent e) {
            x = e.getX ();
            y = e.getY ();
            Figure f = selectFigure (e.getPoint ());
            if (selectedFigure != f && getCursor () == CURSOR_DEFAULT) selectedFigure = f;
            if (selectedFigure == null) setDrawingMode (MODE_NORMAL);
            else {
                selectedFigure.fireMousePressed (e);
                if (e.getButton () == 1) {
                    current = selectedFigure.getLocation ();
                    if (getCursor () != CURSOR_DEFAULT) {
                        handler = getOveredHandler (e.getPoint (), (BoxFigure) selectedFigure);
                        dim = selectedFigure.getSize ();
                        setDrawingMode (MODE_RESIZE_FIGURE);
                    } else {
                        setDrawingMode (MODE_DRAG_FIGURE);
                    }
                }
            }

            if (e.getButton () == MouseEvent.BUTTON1) {
                start = e.getPoint ();
            }
        }

        public void mouseReleased (MouseEvent e) {
            if (selectedFigure != null) selectedFigure.fireMouseReleased (e);
            if (e.getButton () == 1) {
                if (selectedFigure != null && current != null) {
                    adjustDrawingMode (e);
                } else {
                    selectByBox ();
                }
                current = null;
                dim = null;
                dragging = false;
                resizing = false;
                selecting = false;
                setCursor (CURSOR_DEFAULT);
            }

            prevx = prevy = -1;
            start.x = start.y = -1;
            d.width = d.height = 0;
            selecting = false;
        }
    };

    protected MouseMotionListener mml = new MouseMotionListener() {
        public void mouseDragged (MouseEvent e) {
            if (selectedFigure != null) selectedFigure.fireMouseDragged (e);
            switch (drawing_mode) {
                case MODE_DRAWING_FIGURE:
                    break;
                case MODE_DRAG_FIGURE:
                    if (selectedFigure != null && current != null) { // drag a figures
                        dragFigure (e.getPoint ());
                        dragging = true;
                        adjustViewport (current, selectedFigure.getSize ());
                    }
                    break;
                case MODE_RESIZE_FIGURE:
                    if (selectedFigure != null && current != null) { // drag a figures
                        if (handler == -1) dragFigure (e.getPoint ());
                        else resizeFigure (e.getPoint (), handler);
                        resizing = true;
                        adjustViewport (current, dim);
                    }
                    break;
                case MODE_DRAWING_LINE:
                    break;
                case MODE_DRAG_LINE:
                    break;
                default:
                    drawSelectBox (e);
                    break;
            }
        }

        public void mouseMoved (MouseEvent e) {
            if (selectedFigure != null) {
                selectedFigure.fireMouseMoved (e);
                if (selectedFigure instanceof BoxFigure) {
                    BoxFigure bf = (BoxFigure) selectedFigure;
                    setCursor (getCursorAt (e.getPoint (), bf));
                }
            }

            prevx = e.getX ();
            prevy = e.getY ();
        }
    };

    public DrawingCanvas () {
        addMouseListener (ml);
        addMouseMotionListener (mml);
    }

    public void add (Figure figure) {
        figures.addElement (figure);
    }

    public void add (Line line) {
        lines.addElement (line);
        line.canvas = this;
    }

    public void paint (Graphics g) {
        super.paint (g);
        if (lines != null) for (Line line : lines)
            line.paint ((Graphics2D) g);
        if (figures != null) for (Figure figure : figures)
            figure.paint (g);
    }

    public void repaint () {
        super.repaint ();
        if (lines != null) for (Line line : lines)
            line.paint ((Graphics2D) getGraphics ());
        if (figures != null) for (Figure figure : figures)
            figure.paint (getGraphics ());
    }

    public Figure selectFigure (Point p) {
        for (int i = figures.size () - 1; i >= 0; i--) {
            Figure figure = figures.get (i);
            if (figure.getRectangle ().contains (p)) return figure;
        }

        return null;
    }

    public void unselectAll () {
        for (Figure figure : figures) {
            if (figure.isSelected ()) {
                figure.unselect ();
                break;
            }
        }
        for (int i = 0; i < lines.size (); i++) {
            Line line = lines.elementAt (i);
            if (line.isSelected ()) {
                line.unselect ();
                break;
            }
        }

        selectedFigure = null;
        selectedLine = null;
    }

    protected Figure[] getAffectedFigure () {
        ArrayList<Figure> list = new ArrayList<Figure> ();
        Rectangle r = selectedFigure.getRectangle ();
        Point p1 = r.getLocation ();
        Point p2 = new Point (p1.x, p1.y + r.height);
        Point p3 = new Point (p1.x + r.width, p1.y);
        Point p4 = new Point (p3.x, p2.y);
        for (Figure f : figures) {
            if (f == selectedFigure) continue;
            Rectangle fr = f.getRectangle ();
            if (fr.contains (p1) || fr.contains (p2) || fr.contains (p3) || fr.contains (p4)) list.add (f);
        }
        Figure[] fs = new Figure[list.size ()];
        return list.toArray (fs);
    }

    public void drawRect (Point p, Dimension d) {
        Graphics2D g = (Graphics2D) getGraphics ();

        BasicStroke s = (BasicStroke) g.getStroke ();
        float width = s.getLineWidth ();
        float miterLimit = s.getMiterLimit ();
        int cap = s.getEndCap ();
        BasicStroke ns = new BasicStroke (width, cap, BasicStroke.JOIN_ROUND, miterLimit, new float[]{3f, 3f}, BasicStroke.CAP_ROUND);
        g.setStroke (ns);
        g.setXORMode (getBackground ());
        g.setColor (Color.BLACK);
        g.drawRect (p.x, p.y, d.width, d.height);
    }

    protected void adjustViewport (Point p, Dimension dim) {
        Dimension d = (Dimension) dim.clone ();
        int left = p.x;
        int top = p.y;
        int width = left + d.width;
        int height = top + d.height;
        d = getSize ();

        Dimension max = findMaxWidth ();
        if (width > max.width) d.width = width + filler;
        else
            d.width = max.width + filler;
        if (height > max.height) d.height = height + filler;
        else
            d.height = max.height + filler;

        setPreferredSize (d);
        getParent ().doLayout ();
    }

    private void drawSelectBox (MouseEvent e) {
        if (MODE_NORMAL == getDrawingMode ()) {
            Point p = new Point ();
            Dimension d = new Dimension ();
            if (prevx != -1 && prevy != -1) {
                p.x = Math.min (start.x, prevx);
                p.y = Math.min (start.y, prevy);
                d.width = Math.abs (start.x - prevx);
                d.height = Math.abs (start.y - prevy);
                drawRect (p, d);
            }

            prevx = e.getX ();
            prevy = e.getY ();

            p.x = Math.min (start.x, prevx);
            p.y = Math.min (start.y, prevy);
            d.width = Math.abs (start.x - prevx);
            d.height = Math.abs (start.y - prevy);
            drawRect (p, d);
            selecting = true;
        }
    }

    private void selectByBox () {
        if (selecting && getDrawingMode () == MODE_NORMAL) {
            Point p = new Point ();
            Dimension d = new Dimension ();
            p.x = Math.min (start.x, prevx);
            p.y = Math.min (start.y, prevy);
            d.width = Math.abs (start.x - prevx);
            d.height = Math.abs (start.y - prevy);
            drawRect (p, d);
            unselectAll ();
            selecteRectangle (p, d);
            repaint ();
            selecting = false;
        }
    }

    private Dimension findMaxWidth () {
        int x = 0, y = 0;
        for (Figure f : figures) {
            Point p = f.getLocation ();
            Dimension d = f.getSize ();
            if (p.x + d.width > x) x = p.x + d.width;
            if (p.y + d.height > y) y = p.y + d.height;

        }

        return new Dimension (x, y);
    }

    private void adjustDrawingMode (MouseEvent e) {
        Figure f = selectedFigure;
        selectedFigure.fireMouseReleased (e);
        switch (drawing_mode) {
            case MODE_DRAWING_FIGURE:
                break;
            case MODE_DRAG_FIGURE:
                drawRect (current, selectedFigure.getSize ());
                selectedFigure.moveTo (current.x, current.y);
                adjustViewport (current, selectedFigure.getSize ());
                break;
            case MODE_RESIZE_FIGURE:
                drawRect (current, dim);
                selectedFigure.moveTo (current.x, current.y);
                ((BoxFigure) selectedFigure).resizeTo (dim.width, dim.height);
                adjustViewport (current, selectedFigure.getSize ());
                break;
            case MODE_DRAWING_LINE:
                break;
            case MODE_DRAG_LINE:
                break;
            default:
                break;
        }
        unselectAll ();
        figures.remove (f);
        figures.add (f);
        f.select ();
        repaint ();
        selectedFigure = f;
    }

    private void selecteRectangle (Point p, Dimension d) {
        Rectangle r = new Rectangle (p, d);
        for (int i = 0; i < figures.size (); i++) {
            Figure f = figures.elementAt (i);
            if (r.contains (new Rectangle (f.getLocation (), f.getSize ()))) f.select ();
        }
        for (int i = 0; i < lines.size (); i++) {
            Line line = lines.elementAt (i);
            if (r.contains (line.p0) && r.contains (line.p1)) line.selecte ();
        }
    }

    protected void dragFigure (Point p) {
        if (dragging) drawRect (current, selectedFigure.getSize ());
        current.setLocation (current.x + p.x - x, current.y + p.y - y);
        x = p.x;
        y = p.y;
        drawRect (current, selectedFigure.getSize ());
    }

    protected void resizeFigure (Point p, int handler) {
        if (resizing) drawRect (current, dim);
        Dimension d = selectedFigure.getSize ();
        Point pt = selectedFigure.getLocation ();
        switch (handler) {
            case 1:
                current.setLocation (current.x + p.x - x, current.y + p.y - y);
                dim.setSize (d.width + pt.x - current.x, d.height + pt.y - current.y);
                break;
            case 2:
                current.setLocation (current.x, current.y + p.y - y);
                dim.setSize (d.width, d.height + pt.y - current.y);
                break;
            case 3:
                current.setLocation (current.x, current.y + p.y - y);
                dim.setSize (dim.width + p.x - x, d.height + pt.y - current.y);
                break;
            case 4:
                current.setLocation (current.x + p.x - x, current.y);
                dim.setSize (d.width + pt.x - current.x, d.height);
                break;
            case 5:
                dim.setSize (dim.width + p.x - x, d.height);
                break;
            case 6:
                current.setLocation (current.x + p.x - x, current.y);
                dim.setSize (d.width + pt.x - current.x, dim.height + p.y - y);
                break;
            case 7:
                dim.setSize (d.width, dim.height + p.y - y);
                break;
            case 8:
                dim.setSize (dim.width + p.x - x, dim.height + p.y - y);
                break;
        }
        x = p.x;
        y = p.y;
        drawRect (current, dim);
        resizing = true;
    }

    protected int getOveredHandler (Point p, BoxFigure f) {
        Rectangle rec = new Rectangle (f.r.x - BoxFigure.HANDLE_SIZE, f.r.y - BoxFigure.HANDLE_SIZE, BoxFigure.HANDLE_SIZE, BoxFigure.HANDLE_SIZE);
        if (rec.contains (p)) return 1;
        rec.setLocation (f.r.x + (f.r.width - BoxFigure.HANDLE_SIZE) / 2, rec.y);
        if (rec.contains (p)) return 2;
        rec.setLocation (f.r.x + f.r.width, rec.y);
        if (rec.contains (p)) return 3;
        rec.setLocation (f.r.x - BoxFigure.HANDLE_SIZE, f.r.y + (f.r.height - BoxFigure.HANDLE_SIZE) / 2);
        if (rec.contains (p)) return 4;
        rec.setLocation (f.r.x + f.r.width, rec.y);
        if (rec.contains (p)) return 5;
        rec.setLocation (f.r.x - BoxFigure.HANDLE_SIZE, f.r.y + f.r.height);
        if (rec.contains (p)) return 6;
        rec.setLocation (f.r.x + (f.r.width - BoxFigure.HANDLE_SIZE) / 2, rec.y);
        if (rec.contains (p)) return 7;
        rec.setLocation (f.r.x + f.r.width, rec.y);
        if (rec.contains (p)) return 8;
        return -1;
    }

    protected Cursor getCursorAt (Point p, BoxFigure f) {
        int handler = getOveredHandler (p, f);
        switch (handler) {
            case 1:
            case 8:
                return CURSOR_NW_SE;
            case 2:
            case 7:
                return CURSOR_SN;
            case 3:
            case 6:
                return CURSOR_NE_SW;
            case 4:
            case 5:
                return CURSOR_WE;
            default:
                return CURSOR_DEFAULT;
        }
    }

    public int getDrawingMode () {
        return drawing_mode;
    }

    protected void setDrawingMode (int drawing_mode) {
        this.drawing_mode = drawing_mode;
    }

    public void setSelectedFigure (Figure figure) {
        unselectAll ();
        figure.select ();
        selectedFigure = figure;
    }

    public Figure getSelectedFigure () {
        return selectedFigure;
    }

    protected void deleteFigure (Figure figure) {
        if (!figures.contains (figure)) return;
        for (int i = 0; i < lines.size (); i++) {
            Line line = lines.elementAt (i);
            if (line.figure != null) {
                if (line.figure[0] == figure || line.figure[1] == figure) {
                    lines.removeElement (line);
                    if (i > 0) i--;
                }
            }
        }
        figures.removeElement (figure);
    }

    public Figure[] getFigures () {
        Figure[] f = new Figure[figures.size ()];
        return figures.toArray (f);
    }

    public Line[] getLines () {
        Line[] l = new Line[lines.size ()];
        return lines.toArray (l);
    }

    public JFrame getFrame () {
        Component c = getParent ();
        while (c != null) {
            if (c instanceof JFrame) return (JFrame) c;
            c = c.getParent ();
        }

        return null;
    }

    public void setSelectedLine (Line line) {
        unselectAll ();
        selectedLine = line;
        line.selecte ();
    }

    public Line getSelectedLine () {
        return this.selectedLine;
    }

    public void removeAll () {
        super.removeAll ();
        this.selectedLine = null;
        this.selectedFigure = null;
        this.figures.clear ();
        this.lines.clear ();
        repaint ();
    }
}